<?php

namespace App\Service\Cache;

//use Illuminate\Support\Facades\Cache;
use App\Service\Dir;
use App\Service\Cache\CacheFile;
use App\Service\Log\LogFile;
use Illuminate\Support\Facades\Log as Logger;


class BangCache
{

    /**
     * 绑定缓存
     * 1、等更新某个数据的时候，更新所有的绑定缓存
     *   比如更新文章的时候，更新文章绑定的栏目，话题，圈子等缓存
     *
     * 2、某个数据以某个字段作为缓存，比如用户id，而不是id
     *   比如用户中心的数据，一般都是以某个用户为准的，当数据更新的时候，只更新该用户的数据，而不是其他
     *
     */

    protected $cacheBangPath = '';
    protected $store         = 'model';
    protected $error;
    protected $cacheTale;
    protected $cacheClass;

    /**
     * 架构函数
     * @param array $options 参数
     */
    public function __construct(array $options = [])
    {
        $ds = DIRECTORY_SEPARATOR;

        $this->cacheBangPath = getRuntimePath().'bang'.$ds;
    }

    ## 连贯操作
    // 缓存位置
    public function store($store)
    {
        if(!in_array($store, ['response', 'model', 'logic',])){
            return $this->_returnFail('缓存类型不存在!');
        }

        $this->store = $store;

        return $this;
    }

    // 获取错误信息
    public function getError()
    {
        return $this->error;
    }

    // 返回错误
    protected function _returnFail($error)
    {
        $this->error = $error;

        return false;
    }

    // 保存缓存
    public function saveCache($cacheFileName, $data)
    {
        $cacheData = $this->cacheClass()->store($this->store)->set($cacheFileName, $data);

        return $cacheData;
    }

    // 添加绑定
    public function bangAdd($bangFiles, $cacheFileName)
    {
        if(empty($cacheFileName)){
            return $this->_returnFail('请指定缓存的缓存文件名!');
        }

        // 保存绑定的缓存文件
        if(is_array($bangFiles)){
            foreach($bangFiles as $bangFile){
                $this->reSaveBang($bangFile, $cacheFileName);
            }
        } else{
            $this->reSaveBang($bangFiles, $cacheFileName);
        }

        // 绑定日志
        // Logger::write('info', 'add:bang:'.var_export($bangFiles, 1));
        $logger = new LogFile();
        $logger->write('add:bang:'.var_export($bangFiles, 1), 'info');

        return true;
    }

    // 检查绑定  如果没有绑定则重新绑定
    public function bangCheck($bangFiles, $cacheFileName)
    {
        return $this->bangAdd($bangFiles, $cacheFileName);
    }

    ##########################################################################################
    ## 缓存

    // 获取缓存
    public function getCache($cachKey)
    {
        $cacheData = $this->cacheClass()->store($this->store)->get($cachKey);

        return $cacheData;
    }

    // 删除缓存
    public function deleteCache($bangFile)
    {
        $logger = new LogFile();
        $file   = $this->bangFile($bangFile);
        if(is_file($file)){
            // 获取文件内容 避免未知的报错
            $val = $this->file_get_data($file);

            foreach($val as $ii){
                $_cache = explode('.', $ii, 2);
                $store  = $_cache[0];
                if(!in_array($store, ['response', 'model', 'logic',])){
                    $store = $this->store;
                }

                // Logger::write('info', 'delete:bang:'.$ii);
                $logger->write('delete:bang:'.$ii, 'info');
                $this->cacheClass()->store($store)->delete($ii);
            }

            // 删除绑定的缓存文件
            // Logger::write('info', 'delete:file:'.$file);
            $logger->write('delete:file:'.$file, 'info');

            // TODO 暂时不在删除绑定的文件 避免删除绑定文件，而某些缓存文件没有被删除到的情况
            //unlink($file);
        }

        return true;
    }

    // 重新缓存
    public function reCache($_bangFile, $updata = [])
    {
        if(empty($updata)){
            return;
        }
        // 获取缓存的数据
        $cacheData = $this->getCache($_bangFile);
        if(!empty($cacheData)){
            // 判断缓存 如果有值且变化，则更新
            if(is_array($cacheData)){
                foreach($updata as $field => $newVule){
                    // 如果没有这个字段的值 则不更新保存
                    if(!isset($cacheData[$field])){
                        continue;
                    }
                    // 判断值  可以为 +1 -1等
                    if((strpos($newVule, '-') === 0) || (strpos($newVule, '+') === 0)){
                        $newVule = $cacheData[$field] + $newVule;
                    }
                    if($cacheData[$field] != $newVule){
                        $cacheData[$field] = $newVule;
                        $this->saveCache($_bangFile, $cacheData);
                    }
                }
            } elseif(is_object($cacheData)){
                foreach($updata as $field => $newVule){
                    // 如果没有这个字段的值 则不更新保存
                    if(!isset($cacheData->$field)){
                        continue;
                    }
                    // 判断值  可以为 +1 -1等
                    if((strpos($newVule, '-') === 0) || (strpos($newVule, '+') === 0)){
                        $newVule = $cacheData->$field + $newVule;
                    }
                    if($cacheData->$field != $newVule){
                        $cacheData->$field = $newVule;
                        $this->saveCache($_bangFile, $cacheData);
                    }
                }
            }
        }

    }

    // 获取绑定的文件
    public function getBangFile($bangFile)
    {
        $file = $this->bangFile($bangFile);
        // 获取文件内容 避免未知的报错
        $val = $this->file_get_data($file);


        return $val;
    }


    // 生成缓存key
    public function cacheKey($tagName, $key, $param = [])
    {
        //$tagName  = $this->name;  // 模型名称
        $store    = $this->store;
        $preKey   = $store.'.'.$tagName.'.'.$store.'_'.$tagName.'_'.$key;
        $cacheKey = '';

        // 制作key
        foreach($param as $ke => $item){
            if(empty($item)){
                continue;
            }
            $cacheKey .= '_'.$ke.'_';
            $cacheKey .= is_array($item) ? var_export($item, 1) : $item;
        }
        $cacheKey = str_replace(['.', ' '], '_', $cacheKey);
        $cacheKey = str_replace(["\r\n", "\r", "\n"], "", $cacheKey);  // 删除换行

        return $preKey.$cacheKey;
    }

    ##########################################################################################
    ## 绑定

    // 添加绑定与缓存
    public function bangCacheAdd($bangFiles, $info, $cacheFileName)
    {
        // 如果没有需要缓存的内容 或 没有指定绑定文件的时候  不能保存起来
        //（因为没有缓存内容就没有缓存文件，修改的时候就不会更新缓存）
        /*if(empty($info) || empty($bangFiles)){
            return true;
        }*/
        // 用于绑定缓存 $info 为空也能绑定
        if(empty($bangFiles)){
            return true;
        }
        if(empty($cacheFileName)){
            return $this->_returnFail('请指定缓存的缓存文件名!');
        }

        // 保存绑定的缓存文件
        if(is_array($bangFiles)){
            foreach($bangFiles as $bangFile){
                $this->reSaveBang($bangFile, $cacheFileName);
            }
        } else{
            $this->reSaveBang($bangFiles, $cacheFileName);
        }

        // 绑定日志
        //Logger::write('info', 'add:bang:'.var_export($bangFiles, 1));
        $logger = new LogFile();
        $logger->write('add:bang:'.var_export($bangFiles, 1), 'info');

        // 保存缓存文件
        $this->cacheClass()->store($this->store)->set($cacheFileName, $info);

        return true;
    }

    // 删除绑定 + 缓存
    public function bangCacheDelete($bangFiles = '')
    {
        $logger = new LogFile();
        $logger->write('删除绑定的缓存:bang:'.var_export($bangFiles, 1), 'info');
        // 删除详情缓存
        // 删除该id的列表缓存
        if(is_array($bangFiles)){
            foreach($bangFiles as $bangFile){
                //$file = $this->bangFile($bangFile);
                // 删除绑定的文件的缓存
                $this->deleteCache($bangFile);

                /*if(is_file($file)){
                    //Logger::write('info', 'delete:file:'.$file);
                    $logger->write('delete:file:'.$file, 'info');
                    unlink($file);
                }*/
            }
        } else{
            //$file = $this->bangFile($bangFiles);
            // 删除绑定的文件的缓存
            $this->deleteCache($bangFiles);

            /*if(is_file($file)){
                //Logger::write('info', 'delete:file:'.$file);
                $logger->write('delete:file:'.$file, 'info');
                unlink($file);
            }*/
        }

        // 删除绑定的缓存

        // 绑定日志
        //Logger::write('info', 'delete:bang:'.var_export($bangFiles, 1));
        // $logger->write('delete:bang:'.var_export($bangFiles, 1), 'info');


        return true;
    }

    // 列表绑定
    // 根据字段$fields在数据$list中的值来制作绑定
    public function bangCacheFileList($preBang, $fields, $list)
    {
        $list = is_object($list) ? $list->toArray() : $list;
        if(empty($fields)){
            return [];
        }
        // 组合
        $bang = [];
        foreach($list as $item){
            foreach($fields as $_field){
                if(!isset($item[$_field])){
                    continue;
                }

                if($item[$_field] === ''){
                    continue;
                }
                $_value = $item[$_field];
                if(is_string($_value)){
                    // 如果是json
                    $json = json_decode($_value);
                    if($json){
                        $_value = $json;
                    } else{
                        // 如果含有','
                        $_value = explode(',', $_value);
                    }
                }

                // 绑定
                if(is_array($_value)){
                    foreach($_value as $_val){
                        if($_val === ''){
                            continue;
                        }
                        $bangFile = $preBang.'.'.$_field.'.'.$_val;
                        if(!in_array($bangFile, $bang)){
                            $bang[] = $bangFile;
                        }
                    }
                } else{
                    $bangFile = $preBang.'.'.$_field.'.'.$_value;
                    if(!in_array($bangFile, $bang)){
                        $bang[] = $bangFile;
                    }
                }
            }
        }

        return $bang;
    }

    // 详情绑定
    // 根据字段$fields在数据$info，$old中的值来制作绑定（修改的时候会有$old）
    public function bangCacheFileShow($preBang, $fields, $info, $old = [])
    {
        if(empty($fields)){
            return [];
        }

        $info = is_object($info) ? $info->toArray() : $info;
        $old  = is_object($old) ? $old->toArray() : $old;
        // 组合
        $bang = [];
        foreach($fields as $_field){
            // 详情
            if(!empty($info[$_field])){
                $_value = $info[$_field];
                if(is_string($_value)){
                    // 如果是json
                    $json = json_decode($_value);
                    if($json){
                        $_value = $json;
                    } else{
                        // 如果含有','
                        $_value = explode(',', $_value);
                    }
                }
                // 绑定
                $preBangFile = $preBang.'.'.$_field.'.';
                if(is_array($_value)){
                    foreach($_value as $_val){
                        if($_val === ''){
                            continue;
                        }
                        $bangFile = $preBangFile.$_val;
                        if(!in_array($bangFile, $bang)){
                            $bang[] = $bangFile;
                        }
                        // 如果是 id 字段 则还有详情
                        if($_field == 'id'){
                            $bangFile .= '.show';
                            if(!in_array($bangFile, $bang)){
                                $bang[] = $bangFile;
                            }
                        }
                    }
                } else{
                    $bangFile = $preBangFile.$_value;
                    if(!in_array($bangFile, $bang)){
                        $bang[] = $bangFile;
                    }
                    // 如果是 id 字段 则还有详情
                    if($_field == 'id'){
                        $bangFile .= '.show';
                        if(!in_array($bangFile, $bang)){
                            $bang[] = $bangFile;
                        }
                    }
                }
            }
            // 以前的修改
            if(!empty($old[$_field])){
                $_value = $old[$_field];
                if(is_string($_value)){
                    // 如果是json
                    $json = json_decode($_value);
                    if($json){
                        $_value = $json;
                    } else{
                        // 如果含有','
                        $_value = explode(',', $_value);
                    }
                }
                // 绑定
                $bangFile = $preBang.'.'.$_field.'.';
                if(is_array($_value)){
                    foreach($_value as $_val){
                        if($_val === ''){
                            continue;
                        }
                        $bangFile = $bangFile.$_val;
                        if($_field == 'id'){
                            $bangFile .= '.show';
                        }
                        if(!in_array($bangFile, $bang)){
                            $bang[] = $bangFile;
                        }
                    }
                } else{
                    $bangFile = $bangFile.$_value;
                    if($_field == 'id'){
                        $bangFile .= '.show';
                    }
                    if(!in_array($bangFile, $bang)){
                        $bang[] = $bangFile;
                    }
                }
            }
        }

        return $bang;
    }

    ##########################################################################################
    ## 调用

    // 缓存路径
    protected function bangFile(string $key = '')
    {
        if(empty($key)){
            return true;
            //return $this->_returnFail('请指定缓存数据的key!');
        }

        // 如果没有传递id 则保存为列表
        // 如果传递为数字，则为id，解析路径
        // 如果传递字符串，则保存为字符串
        $ds = DIRECTORY_SEPARATOR;
        if(empty($this->cacheBangPath)){
            //$path = '../runtime/table.file/';
            $path = app()->basePath().$ds.'Runtime'.$ds.'bang'.$ds;
            // 重新定义
            $this->cacheBangPath = $path;
        } else{
            $path = $this->cacheBangPath;
        }

        // 区分详情还是列表（TODO 分开详情列表，方便查看或操作）
        $keys = explode('.', $key);
        $pre  = in_array('show', $keys) ? 'show' : 'list';
        $path = $path.$pre.'/';

        // $bangFile 构成：表名.id    表名.字段.112   表名.key
        // $bangFile 构成：表名.122   表名.id.112    表名.list
        $keys = explode('.', $key);
        foreach($keys as $_id){
            if(($_id === '') || ($_id == 'id')){
                continue;
            }
            if(is_string($_id)){
                $path .= $ds.$_id;
            } elseif(is_numeric($_id)){
                // 每三个数字组合分段
                $_ids = str_split($_id, 2);
                foreach($_ids as $_it){
                    if(is_numeric($_it)){
                        $path .= $ds.$_it;
                    }
                }
            }
        }

        $file = $path.'.php';
        $path = dirname($file);
        if(!is_dir($path)){
            (new Dir())->makeDir($path, 0777);
        }

        return $file;
    }

    // 重新保存文件
    protected function reSaveBang($bangFile, $cacheFileName)
    {
        // 如果是 bool 类型的 则不保存了
        if(is_bool($bangFile)){
            return true;
        }
        $file = $this->bangFile($bangFile);

        // 获取文件内容
        if(is_file($file)){
            // 获取文件内容 避免未知的报错
            $val = $this->file_get_data($file);

            // 如果已经存在则不处理
            if(!in_array($cacheFileName, $val)){
                $val[] = $cacheFileName;
                // 重新保存文件
                $this->saveBang($file, $val);
            }
        } else{
            $val = [$cacheFileName];
            // 重新保存文件
            $this->saveBang($file, $val);
        }

        return true;
    }

    // 保存缓存到文件
    protected function saveBang($file, $val)
    {
        //file_put_contents($file, $error . $cline, FILE_APPEND);
        //$cline  = chr(10);
        //$string = "<?php ".$cline.$cline." return ".var_export($val, 1).";".$cline.$cline;
        $string = json_encode($val, JSON_UNESCAPED_UNICODE);
        $this->file_put_data($file, $string);
        //file_put_contents($file, $string);
    }

    // 缓存类
    protected function cacheClass()
    {
        if(empty($this->cacheClass)){
            $this->cacheClass = new CacheFile();
        }

        return $this->cacheClass;
    }

    // 获取文件内容 注意避免未知的报错
    protected function file_get_data($file)
    {
        // 如果文件报错 则为空
        $val = [];
        if(is_file($file)){
            try{
                //$val = include $file;
                $string = file_get_contents($file);
                $val    = json_decode($string, true);
                if(empty($val)){
                    $val = [];
                }
            } catch(\Exception $e){

            }
        }

        return $val;
    }

    // 以锁定的形式 保存文件
    protected function file_put_data($file, $data)
    {
        //$fp = fopen($file, 'a+');
        $fp = fopen($file, 'w+');
        if(!$fp){
            return '文件不存在';
        }

        // 尝试获取独占锁
        if(flock($fp, LOCK_EX)){  // 获得了锁
            // 在这个区域内，文件是被锁定的，其他进程不能修改
            // 如果其他地方需要修改 只能等这个修改结束后才能修改
            fwrite($fp, $data);

            // 解锁
            flock($fp, LOCK_UN);
        } else{
            // 无法获得锁
            return '打开文件失败';
        }
        // 关闭文件
        fclose($fp);

        return true;
    }

}


